import axios, { AxiosProxyConfig, RawAxiosRequestHeaders } from "axios";
import {
  concatMap,
  defer,
  map,
  mergeMap,
  of,
  tap,
  interval,
  take,
  Subject,
  Observable,
  from,
} from "rxjs";
import * as https from "https";
import IConstants from "@/core/constants";
import IHelper from "@/services/helper.service";
import * as path from "path";
import * as fs from "fs-extra";
import * as moment from "moment";
import { randomUUID } from "crypto";
import { CalcProofWorkToken, FreeGpt35AuthSession } from "./proofwork";
import * as mockJs from "mockjs";

export class FreeGpt35 {
  cookies: string = "";
  OaiDeviceId: string = IHelper.hash(32);
  authSession: FreeGpt35AuthSession;
  userAgent: string = IHelper.getUserAgent();
  usedCount: number = 0;
  maxUsedCount: number = 30;
  expiredAt: number = Date.now() + 30 * 60 * 1000;
  status: number = 0;
  messageTemp: string = "";
  getProxy(): AxiosProxyConfig | undefined {
    return { protocol: "http", host: "127.0.0.1", port: 7890 };
  }
  getCookie() {
    return defer(() =>
      axios.get(`${IConstants.openAI.baseURL}/?oai-dm=1`, {
        httpsAgent: new https.Agent({ rejectUnauthorized: false }),
        headers: {
          ...this.getHeaders(),
          accept:
            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
          "content-type": "text/html",
        },
        withCredentials: true,
        proxy: this.getProxy(),
      }),
    ).pipe(
      map((d) => {
        const cookies = (d.headers["set-cookie"] || []).reduce((acc, val) => {
          const [k, v] = val.split(";")[0].split("=");
          return { ...acc, [k]: v };
        }, {});
        cookies["__Secure-next-auth.callback-url"] = encodeURIComponent(
          IConstants.openAI.baseURL,
        );
        this.cookies = Object.keys(cookies)
          .map((k) => `${k}=${cookies[k]}`)
          .join(";");
        return this.cookies;
      }),
    );
  }

  getHeaders(): RawAxiosRequestHeaders {
    return {
      accept: "*/*",
      "accept-language": "zh-CN,zh;q=0.9,zh-Hans;q=0.8,en;q=0.7",
      "oai-language": "en-US",
      origin: IConstants.openAI.baseURL,
      referer: IConstants.openAI.baseURL,
      "sec-ch-ua": `"Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"`,
      "sec-ch-ua-mobilesec-ch-ua-mobile": `?0`,
      "sec-ch-ua-platform": `"Windows"`,
      "sec-fetch-dest": `empty`,
      "sec-fetch-mode": `cors`,
      "sec-fetch-site": `same-origin`,
      "user-agent": this.userAgent,
      cookie: this.cookies,
    };
  }

  freeAuth() {
    const authURL = `${IConstants.openAI.baseURL}/backend-anon/sentinel/chat-requirements`;
    return defer(() =>
      axios.post(
        authURL,
        {},
        {
          headers: {
            ...this.getHeaders(),
            "content-type": "application/json",
            "oai-device-id": this.OaiDeviceId,
          },
          proxy: this.getProxy(),
        },
      ),
    ).pipe(
      map((d) => {
        this.authSession = {
          ...d.data,
          ospt: CalcProofWorkToken(d.data, this.userAgent),
        };
        const tempFile = path.join(
          IConstants.publicDir,
          `uploads/${moment().format("MM_DD_HH_mm_ss")}_${IHelper.hash(6)}.json`,
        );
        fs.ensureFileSync(tempFile);
        fs.writeFileSync(
          tempFile,
          JSON.stringify({
            authSession: this.authSession,
            cookies: this.cookies,
            userAgent: this.userAgent,
            OaiDeviceId: this.OaiDeviceId,
          }),
          { encoding: "utf8" },
        );
        return { dddd: 1234 };
      }),
    );
  }

  buildMessage(modelName: string, content: string, finishReason: string) {
    return {
      id: `chatcmpl-${IHelper.hash(32)}`,
      created: moment().unix(),
      object: "chat.completion.chunk",
      model: modelName || "gpt-3.5-turbo",
      choices: [
        {
          delta: { content },
          index: 0,
          finish_reason: finishReason,
        },
      ],
    };
  }

  formatMessage(
    modelName: string,
    messageStr: string,
    finishReason: string,
  ): any {
    if (messageStr.trim() === "[DONE]") return messageStr;
    let parsed = null;
    try {
      parsed = JSON.parse(messageStr);
    } catch (e) {}
    if (!parsed) {
      return null;
    }
    if (parsed?.message?.author?.role !== "assistant") return null;
    if (parsed?.message?.status !== "in_progress") return null;
    let content = (parsed?.message?.content?.parts?.[0] || "").replace(
      this.messageTemp,
      "",
    );
    this.messageTemp = parsed?.message?.content?.parts?.[0] || "";

    return this.buildMessage(modelName, content, finishReason);
  }

  mappingModel(model: string) {
    const modelMapping = {
      "gpt-3.5-turbo": "text-davinci-002-render-sha",
      "gpt-3.5-turbo-16k": "text-davinci-002-render-sha",
      "gpt-3.5-turbo-16k-0613": "text-davinci-002-render-sha",
      "gpt-3.5-turbo-0301": "text-davinci-002-render-sha",
      "gpt-3.5-turbo-0613": "text-davinci-002-render-sha",
      "gpt-3.5-turbo-1106": "text-davinci-002-render-sha",
    };
    return modelMapping[model] || "text-davinci-002-render-sha";
  }

  buildGptBody(body: any) {
    return {
      action: "next",
      messages: body.messages.map((message) => ({
        author: { role: message.role },
        content: { content_type: "text", parts: [message.content] },
      })),
      parent_message_id: randomUUID(),
      model: this.mappingModel(body.model),
      timezone_offset_min: -180,
      Suggestions: [],
      history_and_training_disabled: true,
      conversation_mode: { kind: "primary_assistant" },
      websocket_request_id: randomUUID(),
    };
  }

  completion(body: any) {
    this.messageTemp = "";
    const uri = `${IConstants.openAI.baseURL}/backend-anon/conversation`;
    const sub = new Subject();
    from(
      axios.post(uri, this.buildGptBody(body), {
        responseType: "stream",
        headers: {
          ...this.getHeaders(),
          "oai-device-id": this.OaiDeviceId,
          "openai-sentinel-chat-requirements-token": this.authSession.token,
          "openai-sentinel-proof-token": this.authSession?.proofofwork.required
            ? this.authSession.ospt
            : "",
        },
        proxy: this.getProxy(),
      }),
    )
      .pipe(
        concatMap((d) => d.data),
        concatMap((d) => {
          // @ts-ignore
          const oriMessage = Buffer.from(d).toString("utf8").substring("data :".length);
          const formatted = this.formatMessage(body.model || "", oriMessage, "");
          if (!formatted) return of(null);
          const isDoneMessage =
            typeof formatted === "string" && formatted.trim() === "[DONE]";
          if (isDoneMessage) {
            sub.next({
              data: this.formatMessage(body.model || "", "", "stop"),
            });
          }
          sub.next({ data: formatted });
          if (isDoneMessage) sub.complete();
          return of(null);
        }),
      )
      .subscribe();
    return sub;
  }

  runCompletion(body: any) {
    return of(null).pipe(
      concatMap(() => this.getCookie()),
      concatMap(() => this.freeAuth()),
      concatMap(() => this.completion(body)),
    );
  }
}
